package com.liuc.practice.mvcframework.servlet;

import com.liuc.practice.mvcframework.annotation.*;
import com.liuc.practice.mvcframework.pojo.Handler;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import javax.naming.spi.DirObjectFactory;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.http.HttpRequest;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @Author Liu Chang
 * @Date 2021/4/14 5:57 下午
 *
 * 前端控制器类：用于加载指定配置文件： springMVC.xml(其中开启扫描注解) 文件
 */
public class MyDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();

    private List<String> classNameList = new ArrayList<>();

    private Map<String, Object> ioc = new HashMap<>();

//    private Map<String, Method> handlerMapping = new HashMap<>();
    private List<Handler> handlerMapping = new ArrayList<>();
    
    
    /**
     * 1. 各种初始化
     * 2. 等待请求
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        doScan(properties.getProperty("scanPackage"));

        try {
            doNewInstance();
            doAutowired();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

        initHandlerMapping();

        System.out.println("初始化完成");
    }

    /**
     * 构造一个 HandlerMapping，映射 url 和 method
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();

            // 该类是否被 @MyController 注解标识
            if (!aClass.isAnnotationPresent(MyController.class)) {
                continue;
            }

            // 判断该类是否被 @MySecurity 标识
            // 如果有标识且 value 不为空，就将 @MySecurity 中的 value 值加入到 handler 的 authority 中
            String[] baseAuthorities = new String[5];
            if (aClass.isAnnotationPresent(MySecurity.class)) {
                MySecurity annotation = aClass.getAnnotation(MySecurity.class);
                String[] values = annotation.value();
                baseAuthorities = values;
            }

            // 该类在被 @MyController 标识的情况下，是否被 @MyRequestMapping 标识
            String baseUrl = "";
            if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping myRequestMappingAnnotation = aClass.getAnnotation(MyRequestMapping.class);
                baseUrl = myRequestMappingAnnotation.value();
            }

            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                Handler handler = new Handler();

                if (method.isAnnotationPresent(MySecurity.class)) {
                    MySecurity mySecurityAnnotation = method.getAnnotation(MySecurity.class);
                    String[] values = mySecurityAnnotation.value();
                    handler.setAuthority(ArrayUtils.addAll(values, baseAuthorities));
                } else {
                    handler.setAuthority(baseAuthorities);
                }

                if (method.isAnnotationPresent(MyRequestMapping.class)) {
                    String methodUrl = method.getAnnotation(MyRequestMapping.class).value();
                    String url = baseUrl + methodUrl;

                    // Handler handler = new Handler(entry.getValue(),  Pattern.compile(url), method);
                    handler.setController(entry.getValue());
                    handler.setUrl(Pattern.compile(url));
                    handler.setMethod(method);
                    handler.setParamsIndexMapping(new HashMap<>());

                    Parameter[] parameters = method.getParameters();
                    for (int i = 0; i < parameters.length; i++) {
                        // 设置默认 HttpServletRequest 和 HttpServletResponse 类的参数名
                        Parameter parameter = parameters[i];
                        if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                            handler.getParamsIndexMapping().put(parameter.getType().getSimpleName(), i);
                        } else {
                            handler.getParamsIndexMapping().put(parameter.getName(), i);
                        }
                    }

                    handlerMapping.add(handler);
                }
            }
        }
    }

    /**
     * 实现依赖注入
     */
    private void doAutowired() throws IllegalAccessException {
        if (ioc.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            // ioc 中每个对象对应的字段信息
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();

            for (Field declaredField : declaredFields) {
                if (!declaredField.isAnnotationPresent(MyAutowired.class)) {
                    continue;
                }

                MyAutowired myAutowiredAnnotation = declaredField.getAnnotation(MyAutowired.class);
                //beanName：注解 value 有的话就是 annotationValue(annotation.value()) ，没有的话就是 interfaceName(interface.getName())
                String beanName = myAutowiredAnnotation.value();

                // 若 @MyAutowired() 中 value 值为空，即默认 default，就通过接口注入
                if ("".equals(beanName.trim())) {
                    beanName = declaredField.getType().getName();
                }

                declaredField.setAccessible(true);
                // 第一个参数是 entry 中的 value， 第二个参数是 ioc 中的实例
                declaredField.set(entry.getValue(), ioc.get(beanName));
            }

        }
    }

    /**
     * 实例化 Bean 对象(基于注解)
     */
    private void doNewInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if (classNameList.size() == 0) return;

        for (int i = 0; i < classNameList.size(); i++) {
            String className = classNameList.get(i); // com.liuc.practice.controller.DemoController

            Class<?> aClass = Class.forName(className);
            // 不对 controller 做其他处理，直接使用类名的首字母小写作为其 Id
            if (aClass.isAnnotationPresent(MyController.class)) {
                String classSimpleName = aClass.getSimpleName(); // DemoController
                String classId = lowerFist(classSimpleName); // demoController
                ioc.put(classId, aClass.newInstance());
            } else if (aClass.isAnnotationPresent(MyService.class)) {
                MyService annotation = aClass.getAnnotation(MyService.class);
                String value = annotation.value();
                if (!value.isEmpty()) {
                    ioc.put(value, aClass.newInstance());
                } else {
                    ioc.put(lowerFist(aClass.getSimpleName()), aClass.newInstance());
                }
                
                // 存放 service 层的接口
                Class<?>[] interfaces = aClass.getInterfaces();
                for (Class<?> anInterface : interfaces) {
                    ioc.put(anInterface.getName(), aClass.newInstance());
                }
            } else {
                continue;
            }
        }

    }

    private String lowerFist(String string) {
        char[] chars = string.toCharArray();
        if (chars[0] >= 'A' && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return string.valueOf(chars);
    }

    /**
     * 扫描类路径
     * @param packageName: com.liuc.practice.mvcapplication
     */
    private void doScan(String packageName) {
        // 找到包路径的真实磁盘路径
        String packagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + packageName.replaceAll("\\.", "/");
        File packages = new File(packagePath);

        File[] files = packages.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                doScan(packageName + "." +file.getName());
            } else if (file.getName().endsWith(".class")) {
                String className = packageName + "." + file.getName().replaceAll(".class", "");
                classNameList.add(className);
            }
        }

    }

    /**
     * 加载配置文件
     *
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    /**
     * 处理请求
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        String requestURI = req.getRequestURI();
//
//        Method method = handlerMapping.get(requestURI);
//
//        method.invoke();

        Handler handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().write("404, not found");
            return;
        }

        // 该 handler 中的参数列表类型
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        // 用于存储前端传入参数列表(反射调用时候用的 method.invoke(obj, args) 中的 args)
        Object[] paramsValue = new Object[parameterTypes.length];

        // 获取前端传入的参数列表
        Map<String, String[]> parameterMap = req.getParameterMap();

        // 从前端获取的参数列表中对比 Handler 中 authority 值进行比对
        String[] names = parameterMap.get("name");
        if (!(handler.getAuthority()[0] == null)) {
            if (ArrayUtils.isEmpty(names)) {
                throw new Error("401: 您无权限访问");
            }

            for (String name : names) {
                if (!ArrayUtils.contains(handler.getAuthority(), name)) {
                    throw new Error("401: 您无权限访问");
                }
            }
        }

        // 填充出了 HttpServletRequest 和 HttpServletResponse 其他类型的参数
        for(Map.Entry<String, String[]> parameter : parameterMap.entrySet()) {
            // 处理同名参数多值： name=1&name=2
            String value = StringUtils.join(parameter.getValue(), ",");

            if(handler.getParamsIndexMapping().containsKey(parameter.getKey())) {
                Integer index = handler.getParamsIndexMapping().get(parameter.getKey());
                // 反射调用时用的数组
                paramsValue[index] = value;
            }
        }

        Integer reqIndex = handler.getParamsIndexMapping().get(HttpServletRequest.class.getSimpleName());
        Integer respIndex = handler.getParamsIndexMapping().get(HttpServletResponse.class.getSimpleName());

        paramsValue[reqIndex] = req;
        paramsValue[respIndex] = resp;

        try {
            handler.getMethod().invoke(handler.getController(), paramsValue);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private Handler getHandler(HttpServletRequest req) {
        if (!handlerMapping.isEmpty()) {
            String requestURI = req.getRequestURI();

            for (Handler handler : handlerMapping) {
                if (handler.getUrl().matcher(requestURI).matches()) {
                    return handler;
                }
            }
        }

        return null;
    }


}
